/*
   ============================================================================
   Programmname: checkers.c
   Autor       : Sebastian Spiller
                 HHBK Gruppe A
   Datum       : 07.05.2011
   Projekt     : C-Spiele
   Version     : 1.0
   Aenderung   : -

   Programmschnittstelle: int main(void)
                          Das Programm gibt den Wert 0, 1, oder 2 zurueck.
                          0: Spielabbruch,
                          1: Sieg Spieler 1,
                          2: Sieg Spieler 2

                          Es werden keine Argumente erwartet.


   Hier wird Brett und Spiellogik von Checkers (Dame) implementiert.

   ============================================================================
*/


/*
============================================================================
Praeprozessoranweisungen
============================================================================
*/
#define _CRT_SECURE_NO_DEPRECATE 1

//#include <math.h>   /* fuer absolutfkt */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define FALSE          0
#define TRUE           1

#define NO_OF_ROWS     8
#define NO_OF_COLS     8

#define B_NO_OF_ROWS   10
#define B_NO_OF_COLS   10

#define ROW            0
#define COL            1

#define START          0
#define TARGET         1

#define ABBRUCH        0
#define SIEG1          1
#define SIEG2          2

#ifdef __unix__
    #define FIELD_A        '-'
    #define FIELD_B        '-'
    #define FIELD_BORDER_A '-'
    #define FIELD_BORDER_B '-'
    #define FIELD_BORDER_C '-'
#else
    #define FIELD_A        '\xB0'
    #define FIELD_B        '\xB1'
    #define FIELD_BORDER_A '\xC5'
    #define FIELD_BORDER_B '\xB3'
    #define FIELD_BORDER_C '\xC4'
#endif

/* Compiler und Systemspezifische Funktionen */
#ifdef __unix__
   #define clrscr() printf("\x1B[2J")
#elif __BORLANDC__ && __MSDOS__
   #include <conio.h>
#elif __WIN32__ || _MSC_VER
   #define clrscr() system("cls")
#else
   #define clrscr() printf("clrscr() - Fehler!!\n")
#endif

/*
============================================================================
Funktionsprototypen
============================================================================
*/
// Parameter-Reihenfolge: aiBoard, aiMove, iPlayer

char changeNumberToToken(           int iNumber);
void changeCurrentPlayer(           void);

int  checkHasCurrentPlayerNoOptions(void);
int  checkIsMoveQueue(              int aiStart[2]);
int  checkIsTokenFromPlayer(        int aiStart[2]);
int  checkIsTokenDame(              int aiStart[2]);
int  checkIsTimeForChange(          void);

int  checkIsDirectionCorrect(       int aiFields[2][2]);
int  checkIsMoveDiagonal(           int aiFields[2][2]);
int  checkJumpMade(                 int aiFields[2][2]);
int  checkMove(                     int aiFields[2][2], int iShowErrors);
int  checkMustMove(                 void);

int  getMove(                       void);

void kickToken(                     void);
void moveToken(                     void);

void printBoard(                    void);
void printMessages(                 void);

int  checkers(                      void);
int  main(                          void);

/*
   ============================================================================
   Globale Variablen
   ============================================================================
*/
int aiBoard[8][8] = {   {1,0,1,0,1,0,1,0},
                        {0,1,0,1,0,1,0,1},
                        {1,0,1,0,1,0,1,0},
                        {0,0,0,0,0,0,0,0},
                        {0,0,0,0,0,0,0,0},
                        {0,2,0,2,0,2,0,2},
                        {2,0,2,0,2,0,2,0},
                        {0,2,0,2,0,2,0,2}
                    };

char acSchachbrett[B_NO_OF_ROWS][B_NO_OF_COLS];

char acErrorMsg[1024];
char acBenutzernameEins[21]                    = "Erster Spieler";
char acBenutzernameZwei[21]                    = "Zweiter Spieler";
char acGlobalCurrentPlayer[21];

int  iGlobalCurrentPlayer                      = 1;
int  aiMove[2][2];


/*
   ============================================================================
   Funktionen der checker.c Datei
   ============================================================================
*/

int main(void)
{
    return checkers();
} // function



/**
 * Erzeugt Brett und Eingabe
 * @param  void
 * @return int
            0: Spielabbruch,
            1: Sieg Spieler 1,
            2: Sieg Spieler 2
 */
int checkers(void)
{
    int iIsWon = FALSE;

    strcpy(acGlobalCurrentPlayer,acBenutzernameEins);

    printf("Welcome 2 Checkers, %s und %s.\n", acBenutzernameEins, acBenutzernameZwei);
    printf("Abbruch mit x oder X und Enter.\n");
    printf("\n\n");


    do
    {
        printBoard();

        // Fehler-Validierung mit Zugeingabe
        if ( TRUE == getMove() )
        {
            // Zug des Spielers mit Fehlerausgabe ueber Global acErrorMsg
            if ( TRUE == checkMove(aiMove, TRUE) );
            {
                moveToken();

                // Uebersprungenen Spielstein entfernen
                if ( ( TRUE == checkIsMoveQueue(aiMove[0]) ) && (TRUE == checkJumpMade(aiMove) )  )
                    kickToken();

                // Wenn ab dem Zielfeld Zugzwang vorliegt, bleibt Spieler an der Reihe
                if ( FALSE == checkIsMoveQueue(aiMove[1]) )
                    changeCurrentPlayer();

                // Hat naechster Spieler noch Zugmoeglichkeiten?
                if ( TRUE == checkHasCurrentPlayerNoOptions() )
                    iIsWon = TRUE;

            } // if
        } // if

        // @todo: else if (ABBRUCH == iIsAbbruch)) 
            //return ABBRUCH;

        clrscr();
        printMessages();

    }
    while (FALSE == iIsWon);

    clrscr();

    printf("%s hat gewonnen!\n", acGlobalCurrentPlayer);

    printBoard();

    printf("Zurueck ins Hauptmenue mit der Entertaste.");

    getchar();

    // Rueckgabe des Siegers fuer Statistik
    return iGlobalCurrentPlayer;
} // function



/*
 * Gibt Fehler und Ereignismeldungen aus
 * @param  int aiMove[2][2] Token-Koordinaten [Row, Column]
 * @param  int iGlobalCurrentPlayer   SpielerID des Spielers, der dran ist
 * @return int TRUE / FALSE  Gibt an, ob noch ein Zug moeglich ist
 */
void printMessages(void)
{

    printf("\nZugkoordinaten:");
    printf("%c%c",  65 + aiMove[START][COL], 49 + aiMove[START][ROW]);
    printf(" nach %c%c\n", 65 + aiMove[TARGET][COL], 49 + aiMove[TARGET][ROW]);

    if ('\0' != acErrorMsg[0])
    {
        printf("Fehler: %s", acErrorMsg);
        printf("%s, ziehe erneut.\n", acGlobalCurrentPlayer);
    } // if
    else
        printf("%s ist an der Reihe.\n", acGlobalCurrentPlayer);

    acErrorMsg[0] = '\0';
} // function


/**
 * Ueberprueft ob Stein vom Spieler ist
 * @param  int aiFields[2][2] Feld
 * @return int TRUE / FALSE
 */
int checkIsTokenFromPlayer(int aiStart[2])
{
    int iReturn = FALSE;

    switch ( aiBoard[aiStart[ROW]][aiStart[COL]] )
    {
        case 1:
        case 3:
            iReturn = ((iGlobalCurrentPlayer == 1) ? TRUE : FALSE );
            break;

        case 2:
        case 4:
            iReturn = ((iGlobalCurrentPlayer == 2) ? TRUE : FALSE );
    } // switch

    return iReturn;
} // function



/**
 * Prueft ob Figur eine Dame ist
 * @param  int aiStart[2] Startfeld
 * @return int TRUE / FALSE
 */
int checkIsTokenDame(int aiStart[2])
{
    if ( 2 < aiBoard[aiStart[ROW]][aiStart[COL]] )
        return TRUE;
    else
        return FALSE;
} // function


/**
 * Ensteht eine Dame?
 * @param  void
 * @return int TRUE / FALSE
 */
int checkIsTimeForChange(void)
{
    if ( (1 == iGlobalCurrentPlayer) && (7 == aiMove[1][0]) )
        return TRUE;
    else if ( (2 == iGlobalCurrentPlayer) && (0 == aiMove[1][0]))
        return TRUE;
    else return FALSE;
} // function


/**
 * Prueft ob Figur korrekt zieht
 * @param  int aiFields[2][2] Start und Zielfeld
 * @return int TRUE / FALSE
 */
int checkIsDirectionCorrect(int aiFields[2][2])
{
    if ( TRUE == checkIsTokenDame(aiFields[START]))
    {
        // Zugrichtung korrekt?
        if ( (1 == iGlobalCurrentPlayer) && (aiFields[TARGET][ROW] < aiFields[START][ROW]) )
            return FALSE;

        // Zugrichtung korrekt?
        if ( (2 == iGlobalCurrentPlayer) && (aiFields[TARGET][ROW] > aiFields[START][ROW])  )
            return FALSE;
    } // if

    return TRUE;
} // function



/**
 * Ueberprueft, ob uebermittelter Spieler weitere Zuege hat
 * @param  void
 * @return int TRUE / FALSE
 */
int checkHasCurrentPlayerNoOptions(void)
{
    int iReturn       = TRUE;
    int iRow          = 0;
    int iCol          = 0;
    int iCounter      = 0;
    int aiFields[2][2];

//@todo: debugging
    return FALSE;

    for (iRow = 0; iRow < NO_OF_ROWS; iRow++)
    {
        for (iCol = 0; iCol < NO_OF_COLS; iCol++)
        {
            aiFields[START][ROW] = iRow;
            aiFields[START][COL] = iCol;

            for (iCounter = -2; iCounter <= 2; iCounter++)
            {
                aiFields[TARGET][ROW] = aiFields[START][ROW] + iCounter;
                aiFields[TARGET][COL] = aiFields[START][COL] + iCounter;
 
               // Ist noch ein Zug moeglich? Bedingung funktioniert als Flag
                
                if   (   (aiFields[TARGET][ROW] >= 0)
                     &&  (aiFields[TARGET][ROW] <= 7)  
                     &&  (aiFields[TARGET][COL] >= 0)
                     &&  (aiFields[TARGET][COL] <= 7)
                     &&  ( TRUE == checkMove(aiFields, FALSE)) )
                {
                    iReturn = FALSE;
                } // if

            } // for
        } // for
    } // for

    return iReturn;
} // function


/**
* Ueberprueft, ob uebermittelter Spieler ziehen muss
* @param  void
* @return int TRUE / FALSE
*/
int checkMustMove(void)
{
    int iReturn       = TRUE;
    int iRow          = 0;
    int iCol          = 0;
    int aiStart[2];

    for (iRow = 0; iRow < NO_OF_ROWS; iRow++)
    {
        for (iCol = 0; (iCol < NO_OF_COLS); iCol++)
        {
            aiStart[ROW] = iRow;
            aiStart[COL] = iCol;

            // Zugzwang?
            if ( TRUE == checkIsMoveQueue(aiStart) )
                iReturn = TRUE;

        } // for
    } // for

    return iReturn;
} // function




/**
  * Ueberprueft ob Zugzwang fuer Parameter - Position vorliegt
  * @param  int aiStart[2][2] Zug mit Start und Zielkoordinaten [Start/Ziel][Row/Column]
  * @return int TRUE / FALSE
  */
int checkIsMoveQueue(int aiStart[2])
{
    int i = 0;
    int j = 0;
    int iReturn = FALSE;
    int aiAroundStart[2][2];

    for (i = -1; i <= 1; i += 2)
    {
        for (j = -1; j <= 1; j += 2)
        {
            aiAroundStart[START][ROW] = aiStart[ROW] + i;
            aiAroundStart[START][COL] = aiStart[COL] + j;

            if (0 !=  aiBoard[aiAroundStart[START][ROW]][aiAroundStart[START][COL]])
            {
                if (FALSE == checkIsTokenFromPlayer(aiStart))
                {
                    if ( 0 == (aiBoard[aiStart[ROW] + 2 * i][aiStart[COL] + 2 * j]) )
                        iReturn = TRUE;
                } // if
            } // if
        } // for
    } // for

    return iReturn;
} // function



/**
 * Figur schlagen
 * @param  void
 * @return void
 */
void kickToken(void)
{
    int iReturn = FALSE;
    int iEnemyRow = 0;
    int iEnemyCol = 0;

    if( 0 < (aiMove[TARGET][ROW] - aiMove[START][ROW]) )
        iEnemyRow = aiMove[START][ROW] + 1;
    else
        iEnemyRow = aiMove[START][ROW] -1;

    if( 0 < (aiMove[TARGET][COL] - aiMove[START][COL]) )
        iEnemyCol = aiMove[START][COL] + 1;
    else
        iEnemyCol = aiMove[START][COL] -1;

    aiBoard[iEnemyRow][iEnemyCol] = 0;

} // function



/**
 * Ueberprueft ob der Zug legal ist
 * @param  int aiFields[2][2] Zug mit Start und Zielkoordinaten [Start/Ziel][Row/Column]
 * @param  int iShowErrors  Wenn Computer mgl Zuege prueft, dann keine Errors senden
 * @return int TRUE / FALSE Ist Zug moeglich?
 */
int checkMove(int aiFields[2][2], int iShowErrors)
{
    int iCheck              = TRUE;
    char acErrorCache[1024];

    acErrorCache[0] = '\0';

    // Startfeld nicht mit Spieler besetzt
    if ( FALSE == checkIsTokenFromPlayer(aiFields[START]) )
    {
        iCheck = FALSE;
        strcat(acErrorCache, "Startfeld nicht mit eigener Figur besetzt.\n");
    } // if


    // ZielFeld besetzt
    if ( 0 != aiBoard[aiFields[TARGET][ROW]][aiFields[TARGET][COL]] )
    {
        iCheck = FALSE;
        strcat(acErrorCache, "Zielfeld ist besetzt\n");
    } // if
 

    // Zug nicht diagonal
    if ( FALSE == checkIsMoveDiagonal(aiFields) )
    {
        iCheck = FALSE;
        strcat(acErrorCache, "Spielzug ist nicht diagonal\n");
    } // if


    // Zugrichtung korrekt?
    if ( (FALSE == checkIsTokenDame(aiFields[START])) && (FALSE == checkIsDirectionCorrect(aiFields)) )
    {
        iCheck = FALSE;
        strcat(acErrorCache, "Zugrichtung falsch \n");
    } // if


    // Sprungweite zu gross
    if (    (  2 < (aiFields[TARGET][ROW] - aiFields[START][ROW]) )
          ||( -2 > (aiFields[TARGET][ROW] - aiFields[START][ROW]) )  )
    {
        iCheck = FALSE;
        strcat(acErrorCache, "Sprung zu weit\n");
    } // if


    // Herrscht Zugzwang?
    if ( (TRUE == iCheck) & (TRUE == checkMustMove()) )
    {
        // Ist der Eigene Zug einer mit Zugzwang und gab es einen Sprung ?
        if (  ( FALSE == checkIsMoveQueue(aiFields[0]) ) && (FALSE == checkJumpMade(aiFields) ) )
        {
            iCheck = FALSE;
            strcat(acErrorCache, "Du musst schlagen!\n");
        } // if
    } // if

    // Fehlernachrichten aus checkMove() weitergeben
    if (TRUE == iShowErrors);
        strcat(acErrorMsg, acErrorCache);

    /* Cache leeren */
    acErrorCache[0] = '\0';

    return iCheck;
} // function


/**
 * Fuehre Spielzug aus
 * @param  void
 * @return void
 */
void moveToken(void)
{
    int iToken = 0;

    iToken = aiBoard[aiMove[START][ROW]][aiMove[START][COL]];

    aiBoard[aiMove[START][ROW]][aiMove[START][COL]]   = 0;
    aiBoard[aiMove[TARGET][ROW]][aiMove[TARGET][COL]] = iToken;

    // mache Figur zur Dame, nur wenn natuerlich einfache Figur
    if ( TRUE == checkIsTimeForChange() )
        aiBoard[aiMove[TARGET][ROW]][aiMove[TARGET][COL]] +=2;

} // function


/**
 * Ueberprueft ob ein Sprung gemacht wurde
 * @param  int aiFields[2][2] Start und Zielfeld
 * @return int TRUE / FALSE
 */
int checkJumpMade(int aiFields[2][2])
{
    if (  (-2 != (aiFields[TARGET][ROW] - aiFields[START][ROW]))
          && ( (aiFields[TARGET][ROW] - aiFields[START][ROW]) != 2 )  )
        return FALSE;
    else
        return TRUE;
} // function


/**
 * Ueberprueft ob ein Sprung gemacht wurde
 * @param  int aiFields[2][2]
 * @return TRUE / FALSE
 */
int checkIsMoveDiagonal(int aiFields[2][2])
{
    int iMoveCol = aiFields[TARGET][COL] - aiFields[START][COL];
    int iMoveRow = aiFields[TARGET][ROW] - aiFields[START][ROW];

    // Zug nicht diagonal
    if ( ( iMoveCol != iMoveRow ) && ((iMoveCol * (-1)) != iMoveRow) )
        return FALSE;

    return TRUE;
} // function


/**
* Zahl zu Spielfigur verwandeln
* @param  int  iNumber Figuren-Zahl
* @return char Figuren-Symbol
*/
char changeNumberToToken(int iNumber)
{
    char cToken = '\0';

    switch(iNumber)
    {
        case 1:
            cToken = 'X';
            break;
        case 2:
            cToken = 'O';
            break;
        case 3:
            cToken = 'x';
            break;
        case 4:
            cToken = 'o';
    } // select
    return cToken;
} // function



/**
* Spielbrett aus Spielfeld-Array erstellen
* @param void
*
*/
void printBoard(void)
{
    int iRow      = 0;
    int iCol      = 0;

 // +++ Fill First 2 Lines +++++++++++++++++++++++++++++++++++++++++++++++++++++

    for (iCol=2;iCol < B_NO_OF_COLS;iCol++) /* Spalten */
        acSchachbrett[0][iCol] = 63+iCol;

    for (iCol=0;iCol< B_NO_OF_COLS;iCol++) /* Spalten */
        acSchachbrett[1][iCol] = FIELD_BORDER_C;

 // --- Fill First 2 Lines -----------------------------------------------------


 // +++ Fill First 2 Columns +++++++++++++++++++++++++++++++++++++++++++++++++++

    for (iRow=2; iRow < B_NO_OF_ROWS; iRow++) /* Zeilen */
    acSchachbrett[iRow][0] = 47 + (11 - iRow);

    acSchachbrett[1][1] = FIELD_BORDER_A;

    for (iRow=0;iRow< B_NO_OF_ROWS;iRow++) /* Zeilen */
    acSchachbrett[iRow][1] = FIELD_BORDER_B;

 // --- Fill First 2 Columns ---------------------------------------------------


 // +++ Brettfelder mit Daten aus dem Spielfeld-Array fuellen ++++++++++++++++++
    for (iRow= 0; iRow < NO_OF_ROWS; iRow++)
    {
        for (iCol = 0; iCol < NO_OF_COLS; iCol++)
        {
            if ( 0 < aiBoard[NO_OF_ROWS - 1 - iRow][iCol] )
                acSchachbrett[iRow + 2][iCol + 2] =
                    changeNumberToToken(aiBoard[NO_OF_ROWS - 1 - iRow][iCol]);
            else
            {
                if (iRow % 2 == 0)
                {
                    if (iCol % 2 == 0)
                        acSchachbrett[iRow + 2][iCol + 2] = FIELD_A;
                    else
                        acSchachbrett[iRow + 2][iCol + 2] = FIELD_B;
                } // if
                else
                {
                    if (iCol % 2 != 0)
                        acSchachbrett[iRow + 2][iCol + 2] = FIELD_A;
                    else
                        acSchachbrett[iRow + 2][iCol + 2] = FIELD_B;
                } // else
           } // else
        } // for
    } // for


// --- Brettfelder mit Daten aus dem Spielfeld-Array fuellen -------------------


// +++ Schachbrett ausgeben ++++++++++++++++++++++++++++++++++++++++++++++++++++
    for ( iRow = 0; iRow < B_NO_OF_ROWS; iRow++)
    {
        for (iCol = 0; iCol < B_NO_OF_COLS; iCol++)
        {
            printf("%c", acSchachbrett[iRow][iCol]);
        } // for
        printf("\n");
    } // for

    printf("\n\n");

// --- Schachbrett ausgeben ----------------------------------------------------

} // function




/**
* Liest Zugeingabe per Tastatur ein
* @param  int          aiMove[2][2]
* @return TRUE / FALSE Eingabe erfolgreich
*/
int getMove(void)
{
    int  iCounter           = 0;    /* Zaehlvariable */
    char acTestStr[256];
    int  iReturn            = TRUE;

    for (iCounter = 0; iCounter <= 1; iCounter++)
    {
        if (0 == iCounter)
            printf("%s, bitte Startfeld eingeben: ", acGlobalCurrentPlayer);
        else
            printf("%s, bitte Zielfeld eingeben: ", acGlobalCurrentPlayer);

        fflush(stdin); 
        scanf("%s", acTestStr);
        fflush(stdin);

        // Abbruch mit x oder X
        if ( ( (acTestStr[0] == 'x') || (acTestStr[0] == 'X') ) && (acTestStr[1] == '\0') )
        {
            clrscr();
            printf("Moechten Sie das Spiel wirklich abbrechen?\n");
            printf("Dann wird keine Statistik gespeichert.\n");
            printf("Abbruch (j) oder weiterspielen (n)?\n");

            do
            {
                scanf("%s", acTestStr);
                fflush(stdin);

                if ( ('j' != acTestStr[0]) || ('n' != acTestStr[0]) )
                {
                    printf("Ich verstehe Ihre Eingabe nicht.\n");
                    printf("Abbruch (Kleiner Buchstabe 'j' + Enter\n");
                    printf("oder weiterspielen (Kleiner Buchstabe 'n' + Enter\n");
                } // if
                else
                {
                    if ('j' != acTestStr[0])
                        return ABBRUCH;
                    else
                        return FALSE; // @todo: Ueberpruefe Sprung zurueck
                } // else
            }
            while ( ('j' != acTestStr[0]) || ('n' != acTestStr[0]) );
        } // if


        // Eingabe nicht zwei Zeichen lang
        if ('\0' != acTestStr[2])
        {
            iReturn = FALSE;
            strcat(acErrorMsg, "Eingabe fuer Zug darf nur zwei Zeichen lang sein.\n");
        } // if



        //  Erstes Zeichen ist kein kleiner bzw grosser Buchstabe A bis H
        if ( ( 97 <= (int)acTestStr[0]) || ((int)acTestStr[0] <= 104 ) )
        {
        }
        else if ( ( 65 <= (int)acTestStr[0]) || ((int)acTestStr[0] <= 72 ) )
        {
        } // else if
        else
        {
            iReturn = FALSE;
            strcat(acErrorMsg, "Erstes Zeichen kein Buchstabe aus (A-H oder a-h).\n");
        } // if


        // Zweites Zeichen ist eine Zahl von 1 bis 8
        if ( ((int)acTestStr[1] < 49 ) || (56 < (int)acTestStr[1]) )
        {
            iReturn = FALSE;
            strcat(acErrorMsg, "Zweites Zeichen keine Zahl zwischen (1-8).\n");
        } // if


        if (TRUE == iReturn)
        {
            // kleines a-h ? dann Wertigkeit des Asciizeichens 'a' abziehen
            if ( (97 <= (int)acTestStr[0]) && ((int)acTestStr[0] <= 104) )
                aiMove[iCounter][COL] = (int)acTestStr[0] - 97;
            else
                aiMove[iCounter][COL] = (int)acTestStr[0] - 65;

            aiMove[iCounter][ROW] = (int)acTestStr[1] - 49;
        } // if
        else
        {
            if (0 == iCounter )
                strcat(acErrorMsg, "Erste Eingabe fehlerhaft:\n");
            else
                strcat(acErrorMsg, "Zweite Eingabe fehlerhaft:\n");
        } // else
    } // for

    return iReturn;

} // function


/**
* Kontrolliert den Spielerwechsel (flipflop zwischen 1 und 2)
* @param  int iGlobalCurrentPlayer ID des aktuellen Spielers
* @return int         ID des folgenden Spielers
*/
void changeCurrentPlayer()
{
   if (2 == iGlobalCurrentPlayer)
   {
        strcpy(acGlobalCurrentPlayer,acBenutzernameEins);
        iGlobalCurrentPlayer = 1;
   } // if
   else
   {
        strcpy(acGlobalCurrentPlayer,acBenutzernameZwei);
        iGlobalCurrentPlayer = 2;
   } // else
} // function



